<?php

class TransactionService extends AbstractService
{
    const RECOMMEND_LIMIT = 30;

    /**
     * @var TransactionService
     */
    private static $instance;

    /**
     * Get instance of TransactionService.
     *
     * @return TransactionService
     */
    public static function get()
    {
        if (!self::$instance) {
            self::$instance = new TransactionService();
        }

        return self::$instance;
    }


    /**
     * Construction.
     */
    protected function __construct()
    {
        parent::__construct();
    }

    /**
     * 处理创建时的逻辑
     *
     * @param int $userId
     * @return void
     */
    public function handleCreate($userId)
    {
    }

    /**
     * 处理登录时的逻辑
     * 1. 更新货架出售信息
     *
     * @param int $userId
     * @return void
     */
    public function handleLogin($userId)
    {
        $this->updateTransactionWithCacheMerge($userId);
    }

    /**
     * 提供当前 service 数据
     *
     * @param int $userId
     * @param boolean $visitMode default false
     * @return array(TransactionShopsVo)
     */
    public function info($userId, $visitMode = false)
    {
        $transactionShops = $this->getTransactionWithCacheMerge($userId);
        return array(
            $transactionShops
        );
    }

    /**
     * 货品上架出售
     *
     * @param int $userId
     * @param int $columnNumber
     * @param int $itemDefId
     * @param int $count
     * @param int $price
     * @throws SzException 20451, 20452, 20453, 20454, 20460
     * @return true
     */
    public function onShelf($userId, $columnNumber, $itemDefId, $count, $price)
    {
        $reqTime = GameService::get()->getReqTime();

        /** @var TransactionShopsVo $transactionShops */
        $transactionShops = SzPersister::get()->getVo($userId, GameConst::ORM_NAME_TRANSACTION_SHOPS);
        if (!$transactionShops) {
            $transactionShops = $this->create($userId);
        }

        // 读取相关配置
        $tradeExpSetting = SzConfig::get()->loadAppConfig(GameConst::CONFIG_BUILDING_TRADE_EXP, $transactionShops->getShopLevel(), true);
        $itemTradeSetting = SzConfig::get()->loadAppConfig(GameConst::CONFIG_ITEM_TRADE, $itemDefId, true);

        // 是否可以上架
        if (!$itemTradeSetting['salable']) {
            throw new SzException(20467, $itemDefId);
        }

        // 上架物品数量超出出售上线
        if ($itemTradeSetting['countLimit'] < $count || $count <= 0) {
            throw new SzException(20453);
        }

        // 栏位越界
        if ($tradeExpSetting['counter'] + $transactionShops->getUnlockNum() < $columnNumber) {
            throw new SzException(20451);
        }

        // 判断是否超过建议售价
        $sellPrice = $price / $count;
        if ($itemTradeSetting['priceLimitLow'] > $sellPrice || $itemTradeSetting['priceLimitHigh'] < $sellPrice) {
            throw new SzException(20460);
        }

        // 判断是否重复上架
        $columnList = $transactionShops->getRawColumnList();
        if (SzUtility::checkArrayKey($columnNumber, $columnList)) {
            throw new SzException(20454);
        }

        // 扣除道具，再进行上架
        $consumeEntityId = EntityService::get()->buildEntityId(GameConst::ENTITY_ACTION_CONSUME, GameConst::ENTITY_TYPE_ITEM, $itemDefId);
        GameService::get()->handleEntity($userId, $consumeEntityId, $count, true);

        // 将道具上架
        $columnList[$columnNumber] = array(
            'state' => 0,
            'itemDefId' => $itemDefId,
            'count' => abs($count),
            'bUid' => 0,
            'lvl' => 0,
            'bName' => '',
            'bHead' => 0,
            'cd' => 0,
            'price' => $price,
            'onShelfTime' => $reqTime,
        );

        // 更新货架信息
        $transactionShops->setColumnList($columnList);
        $transactionShops->setUserLevel(UserService::get()->getUserLevel($userId));
        $transactionShops->setUserName(json_decode(UserService::get()->getTownName($userId)));
        SzPersister::get()->setVo($transactionShops);

        // 更新任务进度
        MissionService::get()->progress($userId, GameConst::ENTITY_ACTION_SHOP_ON_SELL, GameConst::ENTITY_TYPE_NULL, $itemDefId, 1);

        // 数据采集:室外市集上架商品
        StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_USER_ORBIT, array(
            'clientData' => array(
                'tid' => 'Module:FarmersMarket|action:up|',
                'item' => $itemDefId . '_' . abs($count) . '_' . $price,
                'value' => 1
            )
        ));

        // 数据采集:室外市集上架物品消耗
        StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_RESOURCE_INPUT, array(
            'clientData' => array(
                'tid' => $consumeEntityId . ':' . abs($count) . '|',
                'item' => 'FarmersMarket_up_' . $itemDefId
            )
        ));

        return true;
    }

    /**
     * 货品下架出售
     *
     * @param int $userId
     * @param int $columnNumber
     * @throws SzException 20451, 20455
     * @return true
     */
    public function offShelf($userId, $columnNumber)
    {
        GameService::get()->lock(GameConst::CACHE_KEY_TRANSACTION . $userId);
        $transactionShops = $this->getTransactionWithCacheMerge($userId);
        if (!$transactionShops) {
            GameService::get()->unlock(GameConst::CACHE_KEY_TRANSACTION . $userId);
            throw new SzException(20455);
        }

        // 判断商品是否上架
        $columnList = $transactionShops->getRawColumnList();
        if (!SzUtility::checkArrayKey($columnNumber, $columnList)) {
            GameService::get()->unlock(GameConst::CACHE_KEY_TRANSACTION . $userId);
            throw new SzException(20455);
        }

        // 判断是否已经有人购买
        $columnInfo = $columnList[$columnNumber];
        if ($columnInfo['state'] == 1) {
            GameService::get()->unlock(GameConst::CACHE_KEY_TRANSACTION . $userId);
            throw new SzException(20466);
        }

        // 扣除消耗
        $buildingTradeSetting = SzConfig::get()->loadAppConfig(GameConst::CONFIG_BUILDING_TRADE, 28980002, true);
        GameService::get()->handleEntity($userId, GameConst::ENTITY_ACTION_CONSUME_MONEY, $buildingTradeSetting['tearCost'], true);

        // 货架更新
        unset($columnList[$columnNumber]);
        $transactionShops->setColumnList($columnList);
        $transactionShops->setUserLevel(UserService::get()->getUserLevel($userId));
        $transactionShops->setUserName(json_decode(UserService::get()->getTownName($userId)));
        $transactionShops->persist();
        SzPersister::get()->setVo($transactionShops);

        // 先下架，再获得道具
        $getEntityId = EntityService::get()->buildEntityId(GameConst::ENTITY_ACTION_GET, GameConst::ENTITY_TYPE_ITEM, $columnInfo['itemDefId']);
        GameService::get()->handleEntity($userId, $getEntityId, $columnInfo['count'], true);

        // 清除 Redis 中的购买信息
        SzCacheFactory::get()->getAppCache($userId)->delete(GameConst::CACHE_KEY_TRANSACTION_BUY . $userId);
        GameService::get()->unlock(GameConst::CACHE_KEY_TRANSACTION . $userId);

        // 数据采集:室外市集下架商品
        StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_USER_ORBIT, array(
            'clientData' => array(
                'tid' => 'Module:FarmersMarket|action:down|',
                'item' => $columnInfo['itemDefId'] . '_' . abs($columnInfo['count']) . '_' . $columnInfo['price'],
                'value' => 1
            )
        ));

        // 数据采集:室外市集下架商品消耗钻石
        StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_USE_GOLD, array(
            'clientData' => array(
                'item' => 'FarmersMarket_down_' . $columnInfo['itemDefId'],
                'value' => $buildingTradeSetting['tearCost']
            )
        ));

        // 数据采集:室外市集下架物品消耗
        StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_RESOURCE_OUTPUT, array(
            'clientData' => array(
                'tid' => GameConst::ENTITY_ACTION_CONSUME_MONEY . ':' . abs($buildingTradeSetting['tearCost']) . '|',
                'item' => 'FarmersMarket_down_' . $columnInfo['itemDefId']
            )
        ));
        return true;
    }

    /**
     * 获取玩家商铺
     *
     * @param int $userId
     * @throws SzException 20456
     * @return TransactionShopsVo
     */
    public function goToUserShop($userId)
    {
        return $this->getTransactionWithCacheMerge($userId);
    }

    /**
     * 货品上架购买
     *
     * @param int $userId 卖家uid
     * @param int $sellerUserId 卖家uid
     * @param int $columnNumber
     * @param int $itemDefId
     * @param int $count
     * @param int $price
     * @throws SzException 20451, 20452, 20455, 20456, 20457, 20458, 20459, 20460, 20465
     * @return true
     */
    public function buy($userId, $sellerUserId, $columnNumber, $itemDefId, $count, $price)
    {
        GameService::get()->lock(GameConst::CACHE_KEY_TRANSACTION . $sellerUserId);
        // 获取货架实时信息
        $transactionShops = $this->getTransactionWithCacheMerge($sellerUserId);
        if (!$transactionShops) {
            GameService::get()->unlock(GameConst::CACHE_KEY_TRANSACTION . $sellerUserId);
            throw new SzException(20456);
        }

        // 读取相关配置
        $tradeExpSetting = SzConfig::get()->loadAppConfig(GameConst::CONFIG_BUILDING_TRADE_EXP, $transactionShops->getShopLevel(), true);
        if ($tradeExpSetting['counter'] + $transactionShops->getUnlockNum() < $columnNumber) {
            GameService::get()->unlock(GameConst::CACHE_KEY_TRANSACTION . $sellerUserId);
            throw new SzException(20451);
        }

        // 判断商品是否上架
        $columnList = $transactionShops->getRawColumnList();
        if (!SzUtility::checkArrayKey($columnNumber, $columnList)) {
            GameService::get()->unlock(GameConst::CACHE_KEY_TRANSACTION . $sellerUserId);
            throw new SzException(20455);
        }

        // 判断是否已经有人购买
        $columnInfo = $columnList[$columnNumber];
        if ($columnInfo['state'] == 1) {
            GameService::get()->unlock(GameConst::CACHE_KEY_TRANSACTION . $sellerUserId);
            throw new SzException(20459);
        }

        // 判断卖家购买数量和价格是否和买家对该物品设置的数量和价格是否相同
        if ($columnInfo['itemDefId'] != $itemDefId || $columnInfo['count'] != $count || $columnInfo['price'] != $price) {
            GameService::get()->unlock(GameConst::CACHE_KEY_TRANSACTION . $sellerUserId);
            throw new SzException(20465);
        }

        // 购买记录缓存
        GameService::get()->lock(GameConst::CACHE_KEY_TRANSACTION_BUY . $sellerUserId);
        SzCacheFactory::get()->getAppCache($sellerUserId)->hSet(
            GameConst::CACHE_KEY_TRANSACTION_BUY . $sellerUserId,
            $columnNumber,
            array($userId, GameService::get()->getReqTime()),
            SzTime::DAY7
        );
        GameService::get()->unlock(GameConst::CACHE_KEY_TRANSACTION_BUY . $sellerUserId);

        // 扣除买家金币，增加买家物品放入仓库
        GameService::get()->handleEntity($userId, GameConst::ENTITY_ACTION_CONSUME_TOKEN, $price, true);

        // 增加仓库里的货物
        $getEntityId = EntityService::get()->buildEntityId(GameConst::ENTITY_ACTION_GET, GameConst::ENTITY_TYPE_ITEM, $itemDefId);
        GameService::get()->handleEntity($userId, $getEntityId, $count, true);

        // 更新任务进度
        MissionService::get()->progress($userId, GameConst::ENTITY_ACTION_SHOP_BUY, GameConst::ENTITY_TYPE_NULL, $itemDefId, 1);

        // 数据采集
        StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_USER_ORBIT, array(
            'clientData' => array(
                'tid' => 'Module:FarmersMarket|action:buy|',
                'item' => $sellerUserId . '_1_' . $itemDefId,
                'value' => 1
            )
        ));

        // 数据采集:室外市集访问其他玩家购买物品消耗
        StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_RESOURCE_INPUT, array(
            'clientData' => array(
                'tid' => GameConst::ENTITY_ACTION_CONSUME_TOKEN . ':' . abs($price) . '|',
                'item' => 'FarmersMarket_buy_' . $itemDefId
            )
        ));

        // 数据采集:室外市集访问其他玩家购买物品产出
        StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_RESOURCE_OUTPUT, array(
            'clientData' => array(
                'tid' => $getEntityId . ':' . $count . '|',
                'item' => 'FarmersMarket_buy_' . $itemDefId
            )
        ));
        GameService::get()->unlock(GameConst::CACHE_KEY_TRANSACTION . $sellerUserId);
        return true;
    }

    private function create($userId)
    {
        $transactionShops = new TransactionShopsVo(
            $userId, // userId
            GameConst::DEFAULT_JSON_STRING, // columnList
            0, // unlockNum
            json_encode(UserService::get()->getTownName($userId)), // userName
            UserService::get()->getHeadIcon($userId), // userHead
            0, // userLevel
            0, // advertise
            1, // shopLevel
            0, // shopExp
            0, // nextCD
            0, // beginAd
            true
        );

        return $transactionShops;
    }

    /**
     * 购买解锁栏位数量
     *
     * @param int $userId
     * @throws SzException 20456, 20461
     * @return true
     */
    public function payForUnlock($userId)
    {
        $buildingTradeSetting = SzConfig::get()->loadAppConfig(GameConst::CONFIG_BUILDING_TRADE, 28980002, true);

        /**
         * @var TransactionShopsVo $transactionShops
         */
        $transactionShops = SzPersister::get()->getVo($userId, GameConst::ORM_NAME_TRANSACTION_SHOPS);
        if (!$transactionShops) {
            $transactionShops = $this->create($userId);
        }
        // 解锁下一个栏位
        $unlockNumber = $transactionShops->getUnlockNum() + 1;

        // 判断解锁次数是否可解锁
        $unlockNumberIndex = $unlockNumber - 1;
        if (!SzUtility::checkArrayKey($unlockNumberIndex, $buildingTradeSetting['counterBuyCost'])) {
            throw new SzException(20461);
        }
        GameService::get()->handleEntity($userId, GameConst::ENTITY_ACTION_CONSUME_MONEY, $buildingTradeSetting['counterBuyCost'][$unlockNumberIndex], true);

        // 更新玩家数据
        $transactionShops->setUnlockNum($unlockNumber);
        $transactionShops->setUserLevel(UserService::get()->getUserLevel($userId));
        $transactionShops->setUserName(json_decode(UserService::get()->getTownName($userId)));
        SzPersister::get()->setVo($transactionShops);

        // 数据采集:市集商店点击空位解锁
        StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_USER_ORBIT, array(
            'clientData' => array(
                'tid' => 'Module:FarmersMarket|action:unlock|',
                'item' => $unlockNumber,
                'value' => 1
            )
        ));

        // 数据采集:市集商店空位解锁消耗钻石
        StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_USE_GOLD, array(
            'clientData' => array(
                'item' => 'FarmersMarket_unlock',
                'value' => $buildingTradeSetting['counterBuyCost'][$unlockNumberIndex]
            )
        ));

        // 数据采集:市集商店空位解锁
        StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_UPGRADE, array(
            'clientData' => array(
                'tid' => 'objective:FarmersMarket|item:empty|',
                'item' => 'fromlevel:' . ($unlockNumber - 1) . '|changelev:1|targetlev:' . $unlockNumber . '|',
                'value' => 1
            )
        ));

        return true;
    }

    /**
     * 商铺栏位收钱
     *
     * @param int $userId
     * @param int $columnNumber
     * @param int $payForSkip default 0
     * @throws SzException 20455, 20456, 20462, 20463
     * @return true
     */
    public function harvest($userId, $columnNumber, $payForSkip)
    {
        $reqTime = GameService::get()->getReqTime();

        // 更新货架出售信息
        $transactionShops = $this->getTransactionWithCacheMerge($userId);
        if (!$transactionShops) {
            throw new SzException(20456);
        }

        $columnList = $transactionShops->getRawColumnList();
        if (!SzUtility::checkArrayKey($columnNumber, $columnList)) {
            throw new SzException(20455);
        }

        // 判断是否已经售出
        $columnInfo = $columnList[$columnNumber];
        if ($columnInfo['state'] == 0) {
            throw new SzException(20462);
        }

        // 当不需要付给跳过时，则验证是否满足可领取时间
        if ($payForSkip == 0 && $reqTime < $columnInfo['cd']) {
            throw new SzException(20463);
        }

        // 付费跳过
        if ($payForSkip > 0) {
            $remainingCost = GameService::get()->handleSpeedUp($userId, 16990001, $columnInfo['cd'], $reqTime);

            // 数据采集：市集快速获取售卖后奖励消耗钻石
            StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_USE_GOLD, array(
                'clientData' => array(
                    'item' => 'FarmersMarket&speed_reward',
                    'value' => abs($remainingCost)
                )
            ));
        }

        $buildingTradeSetting = SzConfig::get()->loadAppConfig(GameConst::CONFIG_BUILDING_TRADE, 28980002, true);
        $itemTradeSetting = SzConfig::get()->loadAppConfig(GameConst::CONFIG_ITEM_TRADE, $columnInfo['itemDefId'], true);

        // 删除货物
        unset($columnList[$columnNumber]);
        $transactionShops->setColumnList($columnList);
        $transactionShops->setUserLevel(UserService::get()->getUserLevel($userId));
        $transactionShops->setUserName(json_decode(UserService::get()->getTownName($userId)));

        // 清除 Redis 中的购买信息
        SzCacheFactory::get()->getAppCache($userId)->delete(GameConst::CACHE_KEY_TRANSACTION_BUY . $userId);

        // 更新每日经验
        $dailyTradeExp = UserService::get()->getInfo($userId, GameConst::USER_INFO_TYPE_TRADE_EXP_EACH_DAY);
        if (!$dailyTradeExp) {
            $dailyTradeExp = array(
                'exp' => 0, // 累计经验值
                'updateTime' => $reqTime // 更新时间
            );
        }

        // 判断最新更新时间是否小于今天0点
        if ($dailyTradeExp['updateTime'] < GameService::get()->getTodayTimestamp($reqTime)) {
            $dailyTradeExp['exp'] = 0;
        }

        // 判断每日经验获取是否达到上限
        if ($dailyTradeExp['exp'] < $buildingTradeSetting['tradeExpLimitPerDay']) {
            // 计算可获得经验，售出数量 * 配置 tradeExp
            $exp = $itemTradeSetting['tradeExp'];

            // 更新每日经验
            $dailyTradeExp['exp'] += $exp;
            $dailyTradeExp['updateTime'] = $reqTime;
            UserService::get()->updateProfileInfo($userId, GameConst::USER_INFO_TYPE_TRADE_EXP_EACH_DAY, $dailyTradeExp);

            // 店铺增加经验
            $transactionShops = $this->addExp($userId, $exp, $transactionShops);
        }

        SzPersister::get()->setVo($transactionShops);

        // 增加金币
        GameService::get()->handleEntity($userId, GameConst::ENTITY_ACTION_GET_TOKEN, $columnInfo['price'], true);

        // 任务更新
        MissionService::get()->progress($userId, GameConst::ENTITY_ACTION_TRANSACTION_COIN, GameConst::ENTITY_TYPE_TOKEN, GameConst::TOKEN_ID, $columnInfo['price']);

        // 数据采集：市集售罄收款
        StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_USER_ORBIT, array(
            'clientData' => array(
                'tid' => 'Module:FarmersMarket|action:sell_out|',
                'item' => $columnInfo['itemDefId'],
                'value' => 1
            )
        ));

        // 数据采集：市集售罄产出
        StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_RESOURCE_OUTPUT, array(
            'clientData' => array(
                'tid' => GameConst::ENTITY_ACTION_GET_TOKEN . ':' . abs($columnInfo['price']) . '|',
                'item' => 'FarmersMarket_sell_out',
            )
        ));

        if ($payForSkip > 0) {
            // 数据采集：市集快速获取售卖后奖励
            StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_USER_ORBIT, array(
                'clientData' => array(
                    'tid' => 'Module:FarmersMarket|action:speed_reward|',
                    'item' => $columnInfo['itemDefId'] . '_' . $columnInfo['count'] . '_' . $columnInfo['price'],
                    'value' => 1
                )
            ));
        }

        return true;
    }

    /**
     * 更新玩家商铺经验升级商铺等级方法
     *
     * @param int $userId
     * @param int $addExp
     * @param TransactionShopsVo $transactionShops default null
     * @return TransactionShopsVo
     */
    public function addExp($userId, $addExp, $transactionShops = null)
    {
        if (is_null($transactionShops)) {
            $transactionShops = SzPersister::get()->getVo($userId, GameConst::ORM_NAME_TRANSACTION_SHOPS);
        }

        $tradeExpSettings = SzConfig::get()->loadAppConfig(GameConst::CONFIG_BUILDING_TRADE_EXP, null, true);

        // 原始店铺等级与经验
        $oldShopExp = $transactionShops->getShopExp();
        $oldShopLevel = $transactionShops->getShopLevel();

        // 计算店铺等级
        $shopExp = $oldShopExp + $addExp;
        $tradeLevel = $oldShopLevel;
        foreach ($tradeExpSettings as $tradeExpSetting) {
            if ($shopExp < $tradeExpSetting['tradeExp'] || $tradeLevel > $tradeExpSetting['tradeLevel']) {
                continue;
            }
            $tradeLevel = $tradeExpSetting['tradeLevel'];
        }

        // 更新经验与等级
        $transactionShops->setShopExp($shopExp);
        $transactionShops->setShopLevel($tradeLevel);
        if ($_SERVER['is_payment_callback'] ?? false) $transactionShops->persist();

        // 数据采集:市集商店升级
        if ($tradeLevel != $oldShopLevel) {
            StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_UPGRADE, array(
                'clientData' => array(
                    'tid' => 'objective:FarmersMarket|item:FarmersMarket|',
                    'item' => 'fromlevel:' . $oldShopLevel . '|changelev:1|targetlev:' . $tradeLevel . '|',
                    'value' => 1
                )
            ));
        }

        return $transactionShops;
    }

    /**
     * 商铺点击上广告或付费
     *
     * @param int $userId
     * @param int $payForSkip
     * @throws SzException  20456, 20464
     * @return true
     */
    public function openAdvertiseState($userId, $payForSkip)
    {
        $buildingTradeSetting = SzConfig::get()->loadAppConfig(GameConst::CONFIG_BUILDING_TRADE, 28980002, true);

        $reqTime = GameService::get()->getReqTime();

        /**
         * @var TransactionShopsVo $transactionShops
         */
        $transactionShops = SzPersister::get()->getVo($userId, GameConst::ORM_NAME_TRANSACTION_SHOPS);
        if (!$transactionShops) {
            throw new SzException(20456);
        }

        // 当不需要付给跳过时，则验证是否满足可领取时间
        if ($payForSkip == 0 && $reqTime < $transactionShops->getNextCD()) {
            throw new SzException(20463);
        }

        // 付费跳过
        if ($payForSkip > 0) {
            $remainingCost = GameService::get()->handleSpeedUp($userId, 16990001, $transactionShops->getNextCD(), $reqTime);

            // 数据采集：市集商店开启广告消耗钻石
            StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_USE_GOLD, array(
                'clientData' => array(
                    'item' => 'FarmersMarket_advertisement',
                    'value' => abs($remainingCost)
                )
            ));
        }

        // 更新状态
        $transactionShops->setUserLevel(UserService::get()->getUserLevel($userId));
        $transactionShops->setUserName(json_decode(UserService::get()->getTownName($userId)));
        $transactionShops->setAdvertise(1);
        $transactionShops->setNextCD($reqTime + $buildingTradeSetting['adDuration'] + $buildingTradeSetting['adCooldown']);
        $transactionShops->setBeginAd($reqTime);
        SzPersister::get()->setVo($transactionShops);

        // 更新任务
        MissionService::get()->progress($userId, GameConst::ENTITY_ACTION_TRANSACTION_ADV, GameConst::ENTITY_TYPE_NULL, GameConst::ENTITY_PADDING_NULL, 1);

        // 数据采集:市集商店开启广告
        StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_USER_ORBIT, array(
            'clientData' => array(
                'tid' => 'Module:FarmersMarket|action:advertisement|',
                'item' => '',
                'value' => 1
            )
        ));

        return true;
    }

    /**
     * 商铺关闭广告
     *
     * @param int $userId
     * @param int $payForSkip
     * @throws SzException  20456
     * @return true
     */
    public function closeAdvertiseState($userId, $payForSkip)
    {
        $buildingTradeSetting = SzConfig::get()->loadAppConfig(GameConst::CONFIG_BUILDING_TRADE, 28980002, true);

        $reqTime = GameService::get()->getReqTime();

        /**
         * @var TransactionShopsVo $transactionShops
         */
        $transactionShops = SzPersister::get()->getVo($userId, GameConst::ORM_NAME_TRANSACTION_SHOPS);
        if (!$transactionShops) {
            throw new SzException(20456);
        }

        // 当不需要付给跳过时，则验证是否满足可领取时间
        $targetTime = $transactionShops->getBeginAd() + $buildingTradeSetting['adDuration'];
        if ($payForSkip == 0 && $reqTime < $targetTime) {
            throw new SzException(20463);
        }

        // 付费跳过
        if ($payForSkip > 0) {
            $remainingCost = GameService::get()->handleSpeedUp($userId, 16990001, $targetTime, $reqTime);

            // 数据采集：市集商店关闭广告消耗钻石
            StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_USE_GOLD, array(
                'clientData' => array(
                    'item' => 'FarmersMarket_close_advertisement',
                    'value' => abs($remainingCost)
                )
            ));
        }

        // 更新状态
        $transactionShops->setUserLevel(UserService::get()->getUserLevel($userId));
        $transactionShops->setUserName(json_decode(UserService::get()->getTownName($userId)));
        $transactionShops->setNextCD($reqTime + $buildingTradeSetting['adCooldown']);
        $transactionShops->setAdvertise(0);
        $transactionShops->setBeginAd(0);
        SzPersister::get()->setVo($transactionShops);

        // 数据采集:市集商店关闭广告
        StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_USER_ORBIT, array(
            'clientData' => array(
                'tid' => 'Module:FarmersMarket|action:close_advertisement|',
                'item' => 'FarmersMarket_close_advertisement',
                'value' => 1
            )
        ));

        return true;
    }

    /**
     * 刷新商铺推荐池
     * 脚本：刷新一定数量，并且满足条件的玩家商铺到缓存池，数量 = 缓存池数量 * 缓存池内玩家数量
     * 作用：提供给“显示市集列表”接口取得符合条件的玩家商铺用，释放服务器压力，提高接口响应速度
     *
     * @return boolean
     */
    public function flushAdvertisingShopPool()
    {
        $reqTime = GameService::get()->getReqTime();
        $serversConfigs = SzConfig::get()->loadAppConfig('database', 'MySql');
        $levelSettings = SzConfig::get()->loadAppConfig(ModuleProfileConst::CONFIG_LEVELS, null, true);
        $buildingTradeSetting = SzConfig::get()->loadAppConfig(GameConst::CONFIG_BUILDING_TRADE, 28980002, true);

        $pools = array();
        foreach ($serversConfigs as $db) {
            $instance = new SzMySqlDb($db);
            for ($i = 1; $i <= count($levelSettings); $i++) {
                $rows = $instance->select('SELECT `userId`, `columnList`, `userLevel` FROM `ft_transaction_shops` WHERE `advertise` = 1 AND `userLevel` = ' . $i . '  AND `beginAd` > ' . ($reqTime - $buildingTradeSetting['adDuration']) . ' ORDER BY `userLevel` DESC, `beginAd` DESC LIMIT 100');
                foreach ($rows as $row) {
                    /**
                     * @var TransactionShopsVo $transactionShops
                     */
//                    list($transactionShops) = $this->info($row['userId']);
////                    $columnList = json_decode($row['columnList'], true);
//                    $columnList = $transactionShops->getRawColumnList();
//                    foreach ($columnList as $_columnInfo) {
//                        if ($_columnInfo['state'] != 0) {
//                            continue;
//                        }
//
//                        if (!SzUtility::checkArrayKey($i, $pools)) {
//                            $pools[$i] = array();
//                        }
                    $pools[$i][] = $row['userId'];
//                        break;
//                    }
                }
            }

        }

        $this->context->getAppCache()->hMSet(GameConst::CACHE_KEY_TRANSACTION_SHOP, $pools, SzTime::HOURS24);

        return true;
    }

    /**
     * 获取广告中的店铺
     *
     * @param int $userId
     * @param array $filter
     * @param int $adDuration
     * @return boolean | array
     */
    public function getShopInAdvertising($userId, $filter, $adDuration)
    {
        $reqTime = GameService::get()->getReqTime();

        /**
         * @var TransactionShopsVo $transactionShops
         */
//        $transactionShops = SzPersister::get()->getVo($userId, GameConst::ORM_NAME_TRANSACTION_SHOPS);
        list($transactionShops) = $this->info($userId);

        if (
            $transactionShops
            && !in_array($transactionShops->getUserId(), $filter)
            && $transactionShops->getAdvertise() == 1
            && $transactionShops->getBeginAd() + $adDuration > $reqTime
        ) {
            // 只要商铺中有没有卖完的货物就将这个玩家添加到数组中之后立即终止
            foreach ($transactionShops->getRawColumnList() as $_columnInfo) {
                if ($_columnInfo['state'] == 0) {
                    return $transactionShops->toArray();
                    break;
                }
            }
        }

        return false;
    }

    /**
     * 显示市集列表
     *
     * @param int $userId
     * @throws SzException  20456
     * @return array | boolean
     */
    public function getShopList($userId)
    {
        $buildingTradeSetting = SzConfig::get()->loadAppConfig(GameConst::CONFIG_BUILDING_TRADE, 28980002, true);

        // 获取用户的店铺数据，并进行过滤
        $shopList = array();
        $shopFilter = array($userId);

        // 如果开启数据中心推荐，则拉取3条日志
        $appConfig = SzConfig::get()->loadAppConfig('app');
        if ($appConfig['USERCENTOR_OPEN']) {
            $api = ($appConfig['ENV'] == 'DEV') ? GameConst::CONFIG_USER_CENTER_DEV : GameConst::CONFIG_USER_CENTER_LIVE;
            $api .= '?userId=' . $userId . '&gameId=' . $appConfig['GAME_ID'] . '&areaId=9999&maxCount=3';
            try {
                $res = SzUtility::getHttpRequest($api, 3);
            } catch (Exception $e) {
                $res = '{}';
            }
            $data = json_decode($res, true);

            if (SzUtility::checkArrayKey('data', $data)) {
                foreach ($data['data'] as $recommendUserId) {
                    $shop = $this->getShopInAdvertising($recommendUserId, $shopFilter, $buildingTradeSetting['adDuration']);
                    if ($shop !== false) {
                        $shopList[] = $shop;
                        $shopFilter[] = $recommendUserId;
                    }

                    if (count($shopList) >= self::RECOMMEND_LIMIT) {
                        break;
                    }
                }
            }
        }

        // 递归获取广告中的店铺
        $levelSettings = SzConfig::get()->loadAppConfig(ModuleProfileConst::CONFIG_LEVELS, null, true);
        $this->getShopListFromCache(UserService::get()->getUserLevel($userId), $shopList, $shopFilter, $buildingTradeSetting['adDuration'], 0, 1, count($levelSettings));
        $shopList = count($shopList) > 0 ? $shopList : true;
        return $shopList;
    }

    /**
     * 递归获取缓存中的推荐商铺
     * 递归顺序：
     *  userLevel = 50, $direction = 0 => findLevel = 50, $direction = -1
     *  userLevel = 50, $direction = -1 => findLevel = 49, $direction = 1
     *  userLevel = 50, $direction = 1 => findLevel = 51, $direction = -2
     *  userLevel = 50, $direction = -2 => findLevel = 48, $direction = 2
     *  userLevel = 50, $direction = 2 => findLevel = 52, $direction = -3
     *
     * @param int $userLevel
     * @param array $shopList
     * @param array $shopFilter
     * @param boolean $adDuration
     * @param int $direction
     * @param int $min
     * @param int $max
     * @return array
     */
    public function getShopListFromCache($userLevel, &$shopList, &$shopFilter, $adDuration, $direction = 0, $min = 1, $max = 150)
    {
        $findLevel = $userLevel + $direction;
        $shopUserIds = $this->context->getAppCache()->hGet(GameConst::CACHE_KEY_TRANSACTION_SHOP, $findLevel);
        if ($shopUserIds && is_array($shopUserIds)) {
            foreach ($shopUserIds as $shopUserId) {
                $shop = $this->getShopInAdvertising($shopUserId, $shopFilter, $adDuration);
                if ($shop !== false) {
                    $shopList[] = $shop;
                    $shopFilter[] = $shopUserId;
                }

                if (count($shopList) >= self::RECOMMEND_LIMIT) {
                    break;
                }
            }
        }

        // 递归填充
        if (count($shopList) < self::RECOMMEND_LIMIT) {
            // 如果某个方向已经到达极限，则只朝着一个方向进行递归填充
            $nextDirection = ($direction < 0) ? abs($direction) : -abs($direction) - 1;
            $nextFindLevel = $userLevel + $nextDirection;

            if ($nextFindLevel >= $min && $nextFindLevel <= $max) {
                // 在向负方向运动时，每次递归动能 + 1
                $this->getShopListFromCache($userLevel, $shopList, $shopFilter, $adDuration, $nextDirection, $min, $max);
            } else {
                $nextDirection = ($nextFindLevel < $min) ? abs($direction) + 1 : -abs($direction) - 1;
                $nextFindLevel = $userLevel + $nextDirection;

                // 在不确定哪一方向已经达到的极限的情况下，每次递归都需要动能 + 1
                if ($nextFindLevel >= $min && $nextFindLevel <= $max) {
                    $this->getShopListFromCache($userLevel, $shopList, $shopFilter, $adDuration, $nextDirection, $min, $max);
                }
            }
        }

        return $shopList;
    }


    /**
     * 获取货架实时信息
     *
     * @param int $userId
     * @param TransactionShopsVo $transactionShops default null
     * @return TransactionShopsVo
     */
    private function getTransactionWithCacheMerge($userId, $transactionShops = null)
    {
        if (!$transactionShops) {
            $transactionShops = SzPersister::get()->getVo($userId, GameConst::ORM_NAME_TRANSACTION_SHOPS);
        }
        if (!$transactionShops) {
            return $transactionShops;
        }

        // 读取配置
        $buildingTradeSetting = SzConfig::get()->loadAppConfig(GameConst::CONFIG_BUILDING_TRADE, 28980002, true);

        // 货架信息
        $columnList = $transactionShops->getRawColumnList();

        // 将缓存加入到已购买信息中
        $buyerListCaches = SzCacheFactory::get()->getAppCache($userId)->hGetAll(GameConst::CACHE_KEY_TRANSACTION_BUY . $userId);

        foreach ($buyerListCaches as $_columnNumber => $buyerUserInfo) {
            if (!SzUtility::checkArrayKey($_columnNumber, $columnList)) {
                continue;
            }

            // 添加购买者
            list($buyerUserId, $timestamp) = $buyerUserInfo;
            $columnInfo = $columnList[$_columnNumber];
            $columnInfo['state'] = 1;
            $columnInfo['bUid'] = $buyerUserId;
            $columnInfo['lvl'] = UserService::get()->getUserLevel($buyerUserId);
            $columnInfo['bHead'] = UserService::get()->getHeadIcon($buyerUserId);
            $columnInfo['bName'] = UserService::get()->getTownName($buyerUserId);
            $columnInfo['cd'] = $timestamp + $buildingTradeSetting['dealFinishTime'];
            $columnList[$_columnNumber] = $columnInfo;
        }

        $transactionShops->setColumnList($columnList);
        $transactionShops->setUserLevel(UserService::get()->getUserLevel($userId));
        $transactionShops->setUserName(json_decode(UserService::get()->getTownName($userId)));

        return $transactionShops;
    }

    /**
     * 玩家登录，更新货架实时信息
     *
     * @param $userId
     * @return TransactionShopsVo
     */
    private function updateTransactionWithCacheMerge($userId)
    {
        // 更新货架实时信息
        $transactionShops = $this->getTransactionWithCacheMerge($userId);
        SzPersister::get()->setVo($transactionShops);

        // 清除 Redis 中的购买信息
        SzCacheFactory::get()->getAppCache($userId)->delete(GameConst::CACHE_KEY_TRANSACTION_BUY . $userId);

        return $transactionShops;
    }
}
